/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.sondertara.common.bean;

import com.sondertara.common.bean.exception.NestedNullException;
import com.sondertara.common.bean.expression.DefaultResolver;
import com.sondertara.common.bean.expression.Resolver;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.apache.commons.lang3.reflect.MethodUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


import java.beans.IndexedPropertyDescriptor;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.CopyOnWriteArrayList;

/**
 * Utility methods for using Java Reflection APIs to facilitate generic property
 * getter and setter operations on Java objects. Much of this code was
 * originally included in <code>BeanUtils</code>, but has been separated because
 * of the volume of code involved.
 * <p>
 * In general, the objects that are examined and modified using these methods
 * are expected to conform to the property getter and setter method naming
 * conventions described in the JavaBeans Specification (Version 1.0.1). No data
 * type conversions are performed, and there are no usage of any
 * <code>PropertyEditor</code> classes that have been registered, although a
 * convenient way to access the registered classes themselves is included.
 * <p>
 * For the purposes of this class, five formats for referencing a particular
 * property value of a bean are defined, with the <i>default</i> layout of an
 * identifying String in parentheses. However the notation for these formats and
 * how they are resolved is now (since BeanUtils 1.8.0) controlled by the
 * configured {@link Resolver} implementation:
 * <ul>
 * <li><strong>Simple (<code>name</code>)</strong> - The specified
 * <code>name</code> identifies an individual property of a particular JavaBean.
 * The name of the actual getter or setter method to be used is determined using
 * standard JavaBeans instrospection, so that (unless overridden by a
 * <code>BeanInfo</code> class, a property named "xyz" will have a getter method
 * named <code>getXyz()</code> or (for boolean properties only)
 * <code>isXyz()</code>, and a setter method named <code>setXyz()</code>.</li>
 * <li><strong>Nested (<code>name1.name2.name3</code>)</strong> The first name
 * element is used to select a property getter, as for simple references above.
 * The object returned for this property is then consulted, using the same
 * approach, for a property getter for a property named <code>name2</code>, and
 * so on. The property value that is ultimately retrieved or modified is the one
 * identified by the last name element.</li>
 * <li><strong>Indexed (<code>name[index]</code>)</strong> - The underlying
 * property value is assumed to be an array, or this JavaBean is assumed to have
 * indexed property getter and setter methods. The appropriate (zero-relative)
 * entry in the array is selected. <code>List</code> objects are now also
 * supported for read/write. You simply need to define a getter that returns the
 * <code>List</code></li>
 * <li><strong>Mapped (<code>name(key)</code>)</strong> - The JavaBean is
 * assumed to have an property getter and setter methods with an additional
 * attribute of type <code>java.lang.String</code>.</li>
 * <li><strong>Combined (<code>name1.name2[index].name3(key)</code>)</strong> -
 * Combining mapped, nested, and indexed references is also supported.</li>
 * </ul>
 *
 * @version $Id$
 * @see Resolver
 * @since 1.7
 */

public class PropertyUtilsBean {

    private static final PropertyUtilsBean INSTANCE = new PropertyUtilsBean();

    private Resolver resolver = new DefaultResolver();

    // --------------------------------------------------------- Variables

    /**
     * The cache of PropertyDescriptor arrays for beans we have already
     * introspected, keyed by the java.lang.Class of this object.
     */
    private final WeakFastHashMap<Class<?>, BeanIntrospectionData> descriptorsCache;
    private final WeakFastHashMap<Class<?>, FastHashMap> mappedDescriptorsCache;

    /**
     * An empty object array
     */
    private static final Object[] EMPTY_OBJECT_ARRAY = new Object[0];

    /**
     * Log instance
     */
    private final Logger log = LoggerFactory.getLogger(PropertyUtils.class);

    /**
     * The list with BeanIntrospector objects.
     */
    private final List<BeanIntrospector> introspectors;

    // ---------------------------------------------------------- Constructors

    /**
     * Base constructor
     */
    public PropertyUtilsBean() {
        descriptorsCache = new WeakFastHashMap<>();
        descriptorsCache.setFast(true);
        mappedDescriptorsCache = new WeakFastHashMap<>();
        mappedDescriptorsCache.setFast(true);
        introspectors = new CopyOnWriteArrayList<>();
        resetBeanIntrospectors();
    }

    public static PropertyUtilsBean getInstance() {
        return INSTANCE;
    }

    // --------------------------------------------------------- Public Methods

    /**
     * Return the configured {@link Resolver} implementation used by BeanUtils.
     * <p>
     * The {@link Resolver} handles the <i>property name</i> expressions and the
     * implementation in use effectively controls the dialect of the
     * <i>expression language</i> that BeanUtils recongnises.
     * <p>
     * {@link DefaultResolver} is the default implementation used.
     *
     * @return resolver The property expression resolver.
     * @since 1.8.0
     */
    public Resolver getResolver() {

        return resolver;
    }

    /**
     * Configure the {@link Resolver} implementation used by BeanUtils.
     * <p>
     * The {@link Resolver} handles the <i>property name</i> expressions and the
     * implementation in use effectively controls the dialect of the
     * <i>expression language</i> that BeanUtils recongnises.
     * <p>
     * {@link DefaultResolver} is the default implementation used.
     *
     * @param resolver The property expression resolver.
     * @since 1.8.0
     */
    public void setResolver(final Resolver resolver) {

        if (resolver == null) {
            this.resolver = new DefaultResolver();
        } else {
            this.resolver = resolver;
        }
    }

    /**
     * Resets the {@link BeanIntrospector} objects registered at this instance.
     * After this method was called, only the default {@code BeanIntrospector}
     * is registered.
     *
     * @since 1.9
     */
    public final void resetBeanIntrospectors() {

        introspectors.clear();
        introspectors.add(DefaultBeanIntrospector.INSTANCE);
    }

    /**
     * Adds a <code>BeanIntrospector</code>. This object is invoked when the
     * property descriptors of a class need to be obtained.
     *
     * @param introspector the <code>BeanIntrospector</code> to be added (must not be
     *                     <b>null</b>
     * @throws IllegalArgumentException if the argument is <b>null</b>
     * @since 1.9
     */
    public void addBeanIntrospector(final BeanIntrospector introspector) {

        if (introspector == null) {
            throw new IllegalArgumentException("BeanIntrospector must not be null!");
        }
        introspectors.add(introspector);
    }

    /**
     * Removes the specified <code>BeanIntrospector</code>.
     *
     * @param introspector the <code>BeanIntrospector</code> to be removed
     * @return <b>true</b> if the <code>BeanIntrospector</code> existed and
     * could be removed, <b>false</b> otherwise
     * @since 1.9
     */
    public boolean removeBeanIntrospector(final BeanIntrospector introspector) {

        return introspectors.remove(introspector);
    }

    /**
     * Clear any cached property descriptors information for all classes loaded
     * by any class loaders. This is useful in cases where class loaders are
     * thrown away to implement class reloading.
     */
    public void clearDescriptors() {

        descriptorsCache.clear();
        mappedDescriptorsCache.clear();
        Introspector.flushCaches();

    }

    /**
     * <p>
     * Copy property values from the "origin" bean to the "destination" bean for
     * all cases where the property names are the same (even though the actual
     * getter and setter methods might have been customized via
     * <code>BeanInfo</code> classes). No conversions are performed on the
     * actual property values -- it is assumed that the values retrieved from
     * the origin bean are assignment-compatible with the types expected by the
     * destination bean.
     * </p>
     *
     * <p>
     * If the origin "bean" is actually a <code>Map</code>, it is assumed to
     * contain String-valued <strong>simple</strong> property names as the keys,
     * pointing at the corresponding property values that will be set in the
     * destination bean.<strong>Note</strong> that this method is intended to
     * perform a "shallow copy" of the properties and so complex properties (for
     * example, nested ones) will not be copied.
     * </p>
     *
     * <p>
     * Note, that this method will not copy a List to a List, or an Object[] to
     * an Object[]. It's specifically for copying JavaBean properties.
     * </p>
     *
     * @param dest Destination bean whose properties are modified
     * @param orig Origin bean whose properties are retrieved
     * @throws IllegalAccessException    if the caller does not have access to the property accessor
     *                                   method
     * @throws IllegalArgumentException  if the <code>dest</code> or <code>orig</code> argument is
     *                                   null
     * @throws InvocationTargetException if the property accessor method throws an exception
     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
     */
    public void copyProperties(final Object dest, final Object orig) {
        try {
            _copyProperties(dest, orig);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void _copyProperties(final Object dest, final Object orig) throws Exception {

        if (dest == null) {
            throw new IllegalArgumentException("No destination bean specified");
        }
        if (orig == null) {
            throw new IllegalArgumentException("No origin bean specified");
        }

        if (orig instanceof Map) {
            final Iterator<?> entries = ((Map<?, ?>) orig).entrySet().iterator();
            while (entries.hasNext()) {
                final Entry<?, ?> entry = (Entry<?, ?>) entries.next();
                final String name = (String) entry.getKey();
                if (isWriteable(dest, name)) {
                    try {
                        _setSimpleProperty(dest, name, entry.getValue());
                    } catch (final NoSuchMethodException e) {
                        if (log.isDebugEnabled()) {
                            log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
                        }
                    }
                }
            }
        } else /* if (orig is a standard JavaBean) */ {
            final PropertyDescriptor[] origDescriptors = getPropertyDescriptors(orig);
            for (final PropertyDescriptor origDescriptor : origDescriptors) {
                final String name = origDescriptor.getName();
                if (isReadable(orig, name) && isWriteable(dest, name)) {
                    try {
                        final Object value = getSimpleProperty(orig, name);
                        {
                            _setSimpleProperty(dest, name, value);
                        }
                    } catch (final NoSuchMethodException e) {
                        if (log.isDebugEnabled()) {
                            log.debug("Error writing to '" + name + "' on class '" + dest.getClass() + "'", e);
                        }
                    }
                }
            }
        }

    }

    /**
     * <p>
     * Return the entire set of properties for which the specified bean provides
     * a read method. This map contains the unconverted property values for all
     * properties for which a read method is provided (i.e. where the
     * <code>getReadMethod()</code> returns non-null).
     * </p>
     *
     * <p>
     * <strong>FIXME</strong> - Does not account for mapped properties.
     * </p>
     *
     * @param bean Bean whose properties are to be extracted
     * @return The set of properties for the bean
     * @throws IllegalAccessException    if the caller does not have access to the property accessor
     *                                   method
     * @throws IllegalArgumentException  if <code>bean</code> is null
     * @throws InvocationTargetException if the property accessor method throws an exception
     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
     */
    public Map<String, Object> describe(final Object bean) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        final Map<String, Object> description = new HashMap<String, Object>();
        {
            final PropertyDescriptor[] descriptors = getPropertyDescriptors(bean);
            for (final PropertyDescriptor descriptor : descriptors) {
                final String name = descriptor.getName();
                if (descriptor.getReadMethod() != null) {
                    description.put(name, getProperty(bean, name));
                }
            }
        }
        return description;

    }

    /**
     * Return the value of the specified indexed property of the specified bean,
     * with no type conversions. The zero-relative index of the required value
     * must be included (in square brackets) as a suffix to the property name,
     * or <code>IllegalArgumentException</code> will be thrown. In addition to
     * supporting the JavaBeans specification, this method has been extended to
     * support <code>List</code> objects as well.
     *
     * @param bean Bean whose property is to be extracted
     * @param name <code>propertyname[index]</code> of the property value to be
     *             extracted
     * @return the indexed property value
     * @throws IndexOutOfBoundsException if the specified index is outside the valid range for the
     *                                   underlying array or List
     * @throws IllegalAccessException    if the caller does not have access to the property accessor
     *                                   method
     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
     * @throws InvocationTargetException if the property accessor method throws an exception
     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
     */
    public Object getIndexedProperty(final Object bean, String name) {
        try {
            return _getIndexedProperty(bean, name);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Object _getIndexedProperty(final Object bean, String name) throws Exception {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        }

        // Identify the index of the requested individual property
        int index = -1;
        try {
            index = resolver.getIndex(name);
        } catch (final IllegalArgumentException e) {
            throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "' " + e.getMessage());
        }
        if (index < 0) {
            throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "'");
        }

        // Isolate the name
        name = resolver.getProperty(name);

        // Request the specified indexed property value
        return _getIndexedProperty(bean, name, index);

    }

    /**
     * Return the value of the specified indexed property of the specified bean,
     * with no type conversions. In addition to supporting the JavaBeans
     * specification, this method has been extended to support <code>List</code>
     * objects as well.
     *
     * @param bean  Bean whose property is to be extracted
     * @param name  Simple property name of the property value to be extracted
     * @param index Index of the property value to be extracted
     * @return the indexed property value
     * @throws IndexOutOfBoundsException if the specified index is outside the valid range for the
     *                                   underlying property
     * @throws IllegalAccessException    if the caller does not have access to the property accessor
     *                                   method
     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
     * @throws InvocationTargetException if the property accessor method throws an exception
     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
     */
    public Object getIndexedProperty(final Object bean, final String name, final int index) {
        try {
            return _getIndexedProperty(bean, name, index);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Object _getIndexedProperty(final Object bean, final String name, final int index) throws Exception {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null || name.length() == 0) {
            if (bean.getClass().isArray()) {
                return Array.get(bean, index);
            } else if (bean instanceof List) {
                return ((List<?>) bean).get(index);
            }
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        }

        if (name.startsWith("@")) {
            Object f = FieldUtils.readField(bean, trimAnnotations(name));
            if (null == f) {
                if (name.endsWith("?")) {
                    return null;
                } else {
                    throw new NestedNullException();
                }
            }
            if (f.getClass().isArray()) {
                return Array.get(f, index);
            } else if (f instanceof List) {
                return ((List<?>) f).get(index);
            }
        }
        // Retrieve the property descriptor for the specified property
        final PropertyDescriptor descriptor = getPropertyDescriptor(bean, trimAnnotations(name));
        if (descriptor == null) {
            throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
        }

        // Call the indexed getter method if there is one
        if (descriptor instanceof IndexedPropertyDescriptor) {
            Method readMethod = ((IndexedPropertyDescriptor) descriptor).getIndexedReadMethod();
            readMethod = MethodUtils.getAccessibleMethod(readMethod);
            if (readMethod != null) {
                final Object[] subscript = new Object[1];
                subscript[0] = index;
                try {
                    return invokeMethod(readMethod, bean, subscript);
                } catch (final InvocationTargetException e) {
                    if (e.getTargetException() instanceof IndexOutOfBoundsException) {
                        throw (IndexOutOfBoundsException) e.getTargetException();
                    } else {
                        throw e;
                    }
                }
            }
        }

        // Otherwise, the underlying property must be an array
        final Method readMethod = getReadMethod(bean.getClass(), descriptor);
        if (readMethod == null) {
            throw new NoSuchMethodException("Property '" + name + "' has no " + "getter method on bean class '" + bean.getClass() + "'");
        }

        // Call the property getter and return the value
        final Object value = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
        if (null == value && name.endsWith("?")) {
            return null;
        }
        if (!value.getClass().isArray()) {
            if (!(value instanceof List)) {
                throw new IllegalArgumentException("Property '" + name + "' is not indexed on bean class '" + bean.getClass() + "'");
            } else {
                // get the List's value
                return ((List<?>) value).get(index);
            }
        } else {
            // get the array's value
            try {
                return Array.get(value, index);
            } catch (final ArrayIndexOutOfBoundsException e) {
                throw new ArrayIndexOutOfBoundsException("Index: " + index + ", Size: " + Array.getLength(value) + " for property '" + name + "'");
            }
        }

    }

    /**
     * Return the value of the specified mapped property of the specified bean,
     * with no type conversions. The key of the required value must be included
     * (in brackets) as a suffix to the property name, or
     * <code>IllegalArgumentException</code> will be thrown.
     *
     * @param bean Bean whose property is to be extracted
     * @param name <code>propertyname(key)</code> of the property value to be
     *             extracted
     * @return the mapped property value
     * @throws IllegalAccessException    if the caller does not have access to the property accessor
     *                                   method
     * @throws InvocationTargetException if the property accessor method throws an exception
     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
     */
    public Object getMappedProperty(final Object bean, String name) throws NoSuchFieldException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        return _getMappedProperty(bean, name);
    }

    public Object _getMappedProperty(final Object bean, String name) throws NoSuchFieldException, InvocationTargetException, IllegalAccessException, NoSuchMethodException {
        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        }

        // Identify the key of the requested individual property
        String key = null;
        try {
            key = resolver.getKey(name);
        } catch (final IllegalArgumentException e) {
            throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "' " + e.getMessage());
        }
        if (key == null) {
            throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "'");
        }

        // Isolate the name
        name = resolver.getProperty(name);

        // Request the specified indexed property value
        return _getMappedProperty(bean, name, key);

    }

    /**
     * Return the value of the specified mapped property of the specified bean,
     * with no type conversions.
     *
     * @param bean Bean whose property is to be extracted
     * @param name Mapped property name of the property value to be extracted
     * @param key  Key of the property value to be extracted
     * @return the mapped property value
     * @throws IllegalAccessException    if the caller does not have access to the property accessor
     *                                   method
     * @throws InvocationTargetException if the property accessor method throws an exception
     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
     */
    public Object getMappedProperty(final Object bean, final String name, final String key) {
        try {
            return _getMappedProperty(bean, name, key);
        } catch (final RuntimeException e) {
            throw e;
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Object _getMappedProperty(final Object bean, final String name, final String key) throws NoSuchFieldException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        }
        if (key == null) {
            throw new IllegalArgumentException("No key specified for property '" + name + "' on bean class " + bean.getClass() + "'");
        }

        Object result = null;

        if (name.startsWith("@")) {
            Object invokeResult = FieldUtils.readField(bean, trimAnnotations(name));
            if (null == invokeResult) {
                if (name.endsWith("?")) {
                    return null;
                } else {
                    throw new NestedNullException();
                }
            }
            if (invokeResult instanceof Map) {
                return ((Map<?, ?>) invokeResult).get(key);
            } else {
                throw new NoSuchFieldException("Field '" + name + "' is not mapped in '" + bean.getClass() + "'");
            }
        }
        // Retrieve the property descriptor for the specified property
        final PropertyDescriptor descriptor = getPropertyDescriptor(bean, trimAnnotations(name));
        if (descriptor == null) {
            throw new NoSuchMethodException("Unknown property '" + name + "'+ on bean class '" + bean.getClass() + "'");
        } else if (descriptor instanceof MappedPropertyDescriptor) {
            // Call the keyed getter method if there is one
            Method readMethod = ((MappedPropertyDescriptor) descriptor).getMappedReadMethod();
            readMethod = MethodUtils.getAccessibleMethod(readMethod);
            if (readMethod != null) {
                final Object[] keyArray = new Object[1];
                keyArray[0] = key;
                result = invokeMethod(readMethod, bean, keyArray);
            } else {
                throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method on bean class '" + bean.getClass() + "'");
            }
        } else {
            /* means that the result has to be retrieved from a map */
            final Method readMethod = getReadMethod(bean.getClass(), descriptor);
            if (readMethod != null) {
                final Object invokeResult = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
                /* test and fetch from the map */
                if (invokeResult instanceof Map) {
                    result = ((Map<?, ?>) invokeResult).get(key);
                }
            } else {
                throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method on bean class '" + bean.getClass() + "'");
            }
        }
        return result;

    }

    /**
     * Trimming annotations fromt he beggining or the end of the attribute/field name
     */
    private String trimAnnotations(String name) {
        if (name.startsWith("@")) {
            name = name.substring(1);
        }
        if (name.endsWith("?")) {
            name = name.substring(0, name.length() - 1);
        }
        return name;
    }

    /**
     * <p>
     * Return the mapped property descriptors for this bean class.
     * </p>
     *
     * <p>
     * <strong>FIXME</strong> - Does not work with DynaBeans.
     * </p>
     *
     * @param beanClass Bean class to be introspected
     * @return the mapped property descriptors
     * @deprecated This method should not be exposed
     */
    @Deprecated
    public FastHashMap getMappedPropertyDescriptors(final Class<?> beanClass) {

        if (beanClass == null) {
            return null;
        }

        // Look up any cached descriptors for this bean class
        return mappedDescriptorsCache.get(beanClass);

    }

    /**
     * <p>
     * Return the mapped property descriptors for this bean.
     * </p>
     *
     * <p>
     * <strong>FIXME</strong> - Does not work with DynaBeans.
     * </p>
     *
     * @param bean Bean to be introspected
     * @return the mapped property descriptors
     * @deprecated This method should not be exposed
     */
    @Deprecated
    public FastHashMap getMappedPropertyDescriptors(final Object bean) {

        if (bean == null) {
            return null;
        }
        return getMappedPropertyDescriptors(bean.getClass());

    }

    /**
     * Return the value of the (possibly nested) property of the specified name,
     * for the specified bean, with no type conversions.
     *
     * @param bean Bean whose property is to be extracted
     * @param name Possibly nested name of the property to be extracted
     * @return the nested property value
     * @throws IllegalAccessException    if the caller does not have access to the property accessor
     *                                   method
     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
     * @throws NestedNullException       if a nested reference to a property returns null
     * @throws InvocationTargetException if the property accessor method throws an exception
     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
     */
    public Object getNestedProperty(final Object bean, final String name) {
        try {
            return _getNestedProperty(bean, name);
        } catch (final RuntimeException e) {
            throw e;
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Object _getNestedProperty(Object bean, String name) throws Exception {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        }

        // Resolve nested references
        while (resolver.hasNested(name)) {
            final String next = resolver.next(name);
            Object nestedBean = null;
            if (bean instanceof Map) {
                nestedBean = getPropertyOfMapBean((Map<?, ?>) bean, next);
            } else if (resolver.isMapped(next)) {
                nestedBean = getMappedProperty(bean, next);
            } else if (resolver.isIndexed(next)) {
                nestedBean = _getIndexedProperty(bean, next);
            } else {
                nestedBean = _getSimpleProperty(bean, next);
            }
            if (nestedBean == null) {
                if (resolver.isMapped(next)) {
                    String prop = resolver.getProperty(next);
                    if (prop.endsWith("?")) {
                        return null;
                    }
                } else if (resolver.isIndexed(next)) {
                    String prop = resolver.getProperty(next);
                    if (prop.endsWith("?")) {
                        return null;
                    }
                }
                if (next.endsWith("?")) {
                    return null;
                } else {
                    throw new NestedNullException("Null property value for '" + name + "' on bean class '" + bean.getClass() + "'");
                }
            }
            bean = nestedBean;
            name = resolver.remove(name);
        }

        if (bean instanceof Map) {
            bean = getPropertyOfMapBean((Map<?, ?>) bean, name);
        } else if (resolver.isMapped(name)) {
            bean = getMappedProperty(bean, name);
        } else if (resolver.isIndexed(name)) {
            bean = getIndexedProperty(bean, name);
        } else {
            bean = getSimpleProperty(bean, name);
        }
        return bean;

    }

    /**
     * This method is called by getNestedProperty and setNestedProperty to
     * define what it means to get a property from an object which implements
     * Map. See setPropertyOfMapBean for more information.
     *
     * @param bean         Map bean
     * @param propertyName The property name
     * @return the property value
     * @throws IllegalArgumentException  when the propertyName is regarded as being invalid.
     * @throws IllegalAccessException    just in case subclasses override this method to try to access
     *                                   real getter methods and find permission is denied.
     * @throws InvocationTargetException just in case subclasses override this method to try to access
     *                                   real getter methods, and find it throws an exception when
     *                                   invoked.
     * @throws NoSuchMethodException     just in case subclasses override this method to try to access
     *                                   real getter methods, and want to fail if no simple method is
     *                                   available.
     * @since 1.8.0
     */
    protected Object getPropertyOfMapBean(final Map<?, ?> bean, String propertyName) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {

        if (resolver.isMapped(propertyName)) {
            final String name = resolver.getProperty(propertyName);
            if (name == null || name.length() == 0) {
                propertyName = resolver.getKey(propertyName);
            }
        }

        if (resolver.isIndexed(propertyName) || resolver.isMapped(propertyName)) {
            throw new IllegalArgumentException("Indexed or mapped properties are not supported on" + " objects of type Map: " + propertyName);
        }

        return bean.get(propertyName);
    }

    /**
     * Return the value of the specified property of the specified bean, no
     * matter which property reference format is used, with no type conversions.
     *
     * @param bean Bean whose property is to be extracted
     * @param name Possibly indexed and/or nested name of the property to be
     *             extracted
     * @return the property value
     * @throws IllegalAccessException    if the caller does not have access to the property accessor
     *                                   method
     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
     * @throws InvocationTargetException if the property accessor method throws an exception
     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
     */
    public Object getProperty(final Object bean, final String name) {
        try {
            return _getNestedProperty(bean, name);
        } catch (final RuntimeException e) {
            throw e;
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Object _getProperty(final Object bean, final String name) throws Exception {

        return _getNestedProperty(bean, name);

    }

    /**
     * <p>
     * Retrieve the property descriptor for the specified property of the
     * specified bean, or return <code>null</code> if there is no such
     * descriptor. This method resolves indexed and nested property references
     * in the same manner as other methods in this class, except that if the
     * last (or only) name element is indexed, the descriptor for the last
     * resolved property itself is returned.
     * </p>
     *
     * <p>
     * <strong>FIXME</strong> - Does not work with DynaBeans.
     * </p>
     *
     * <p>
     * Note that for Java 8 and above, this method no longer return
     * IndexedPropertyDescriptor for {@link List}-typed properties, only for
     * properties typed as native array. (BEANUTILS-492).
     *
     * @param bean Bean for which a property descriptor is requested
     * @param name Possibly indexed and/or nested name of the property for which
     *             a property descriptor is requested
     * @return the property descriptor
     * @throws IllegalAccessException    if the caller does not have access to the property accessor
     *                                   method
     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
     * @throws IllegalArgumentException  if a nested reference to a property returns null
     * @throws InvocationTargetException if the property accessor method throws an exception
     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
     */
    public PropertyDescriptor getPropertyDescriptor(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        }

        // Resolve nested references
        while (resolver.hasNested(name)) {
            final String next = resolver.next(name);
            final Object nestedBean = getProperty(bean, next);
            if (nestedBean == null) {
                throw new NestedNullException("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'");
            }
            bean = nestedBean;
            name = resolver.remove(name);
        }

        // Remove any subscript from the final name value
        name = resolver.getProperty(name);

        // Look up and return this property from our cache
        // creating and adding it to the cache if not found.
        if (name == null) {
            return null;
        }

        final BeanIntrospectionData data = getIntrospectionData(bean.getClass());
        PropertyDescriptor result = data.getDescriptor(name);
        if (result != null) {
            return result;
        }

        FastHashMap mappedDescriptors = getMappedPropertyDescriptors(bean);
        if (mappedDescriptors == null) {
            mappedDescriptors = new FastHashMap();
            mappedDescriptors.setFast(true);
            mappedDescriptorsCache.put(bean.getClass(), mappedDescriptors);
        }
        result = (PropertyDescriptor) mappedDescriptors.get(name);
        if (result == null) {
            // not found, try to create it
            try {
                result = new MappedPropertyDescriptor(name, bean.getClass());
            } catch (final IntrospectionException ie) {
                /*
                 * Swallow IntrospectionException TODO: Why?
                 */
            }
            if (result != null) {
                mappedDescriptors.put(name, result);
            }
        }

        return result;

    }

    /**
     * <p>
     * Retrieve the property descriptors for the specified class, introspecting
     * and caching them the first time a particular bean class is encountered.
     * </p>
     *
     * <p>
     * <strong>FIXME</strong> - Does not work with DynaBeans.
     * </p>
     *
     * @param beanClass Bean class for which property descriptors are requested
     * @return the property descriptors
     * @throws IllegalArgumentException if <code>beanClass</code> is null
     */
    public PropertyDescriptor[] getPropertyDescriptors(final Class<?> beanClass) {

        return getIntrospectionData(beanClass).getDescriptors();

    }

    /**
     * <p>
     * Retrieve the property descriptors for the specified bean, introspecting
     * and caching them the first time a particular bean class is encountered.
     * </p>
     *
     * <p>
     * <strong>FIXME</strong> - Does not work with DynaBeans.
     * </p>
     *
     * @param bean Bean for which property descriptors are requested
     * @return the property descriptors
     * @throws IllegalArgumentException if <code>bean</code> is null
     */
    public PropertyDescriptor[] getPropertyDescriptors(final Object bean) {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        return getPropertyDescriptors(bean.getClass());

    }

    /**
     * <p>
     * Return the Java Class repesenting the property editor class that has been
     * registered for this property (if any). This method follows the same name
     * resolution rules used by <code>getPropertyDescriptor()</code>, so if the
     * last element of a name reference is indexed, the property editor for the
     * underlying property's class is returned.
     * </p>
     *
     * <p>
     * Note that <code>null</code> will be returned if there is no property, or
     * if there is no registered property editor class. Because this return
     * value is ambiguous, you should determine the existence of the property
     * itself by other means.
     * </p>
     *
     * <p>
     * <strong>FIXME</strong> - Does not work with DynaBeans.
     * </p>
     *
     * @param bean Bean for which a property descriptor is requested
     * @param name Possibly indexed and/or nested name of the property for which
     *             a property descriptor is requested
     * @return the property editor class
     * @throws IllegalAccessException    if the caller does not have access to the property accessor
     *                                   method
     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
     * @throws IllegalArgumentException  if a nested reference to a property returns null
     * @throws InvocationTargetException if the property accessor method throws an exception
     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
     */
    public Class<?> getPropertyEditorClass(final Object bean, final String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        }

        final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
        if (descriptor != null) {
            return descriptor.getPropertyEditorClass();
        } else {
            return null;
        }

    }

    /**
     * Return the Java Class representing the property type of the specified
     * property, or <code>null</code> if there is no such property for the
     * specified bean. This method follows the same name resolution rules used
     * by <code>getPropertyDescriptor()</code>, so if the last element of a name
     * reference is indexed, the type of the property itself will be returned.
     * If the last (or only) element has no property with the specified name,
     * <code>null</code> is returned.
     * <p>
     * If the property is an indexed property (e.g. <code>String[]</code>), this
     * method will return the type of the items within that array. Note that
     * from Java 8 and newer, this method do not support such index types from
     * items within an Collection, and will instead return the collection type
     * (e.g. java.util.List) from the getter mtethod.
     *
     * @param bean Bean for which a property descriptor is requested
     * @param name Possibly indexed and/or nested name of the property for which
     *             a property descriptor is requested
     * @return The property type
     * @throws IllegalAccessException    if the caller does not have access to the property accessor
     *                                   method
     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
     * @throws IllegalArgumentException  if a nested reference to a property returns null
     * @throws InvocationTargetException if the property accessor method throws an exception
     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
     */
    public Class<?> getPropertyType(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        }

        // Resolve nested references
        while (resolver.hasNested(name)) {
            final String next = resolver.next(name);
            final Object nestedBean = getProperty(bean, next);
            if (nestedBean == null) {
                throw new NestedNullException("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'");
            }
            bean = nestedBean;
            name = resolver.remove(name);
        }

        // Remove any subscript from the final name value
        name = resolver.getProperty(name);

        final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
        if (descriptor == null) {
            return null;
        } else if (descriptor instanceof IndexedPropertyDescriptor) {
            return ((IndexedPropertyDescriptor) descriptor).getIndexedPropertyType();
        } else if (descriptor instanceof MappedPropertyDescriptor) {
            return ((MappedPropertyDescriptor) descriptor).getMappedPropertyType();
        } else {
            return descriptor.getPropertyType();
        }

    }

    /**
     * <p>
     * Return an accessible property getter method for this property, if there
     * is one; otherwise return <code>null</code>.
     * </p>
     *
     * <p>
     * <strong>FIXME</strong> - Does not work with DynaBeans.
     * </p>
     *
     * @param descriptor Property descriptor to return a getter for
     * @return The read method
     */
    public Method getReadMethod(final PropertyDescriptor descriptor) {

        return MethodUtils.getAccessibleMethod(descriptor.getReadMethod());

    }

    /**
     * <p>
     * Return an accessible property getter method for this property, if there
     * is one; otherwise return <code>null</code>.
     * </p>
     *
     * <p>
     * <strong>FIXME</strong> - Does not work with DynaBeans.
     * </p>
     *
     * @param clazz      The class of the read method will be invoked on
     * @param descriptor Property descriptor to return a getter for
     * @return The read method
     */
    Method getReadMethod(final Class<?> clazz, final PropertyDescriptor descriptor) {

        return MethodUtils.getAccessibleMethod(descriptor.getReadMethod());
    }

    /**
     * Return the value of the specified simple property of the specified bean,
     * with no type conversions.
     *
     * @param bean Bean whose property is to be extracted
     * @param name Name of the property to be extracted
     * @return The property value
     * @throws IllegalAccessException    if the caller does not have access to the property accessor
     *                                   method
     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
     * @throws IllegalArgumentException  if the property name is nested or indexed
     * @throws InvocationTargetException if the property accessor method throws an exception
     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
     */
    public Object getSimpleProperty(final Object bean, final String name) {
        try {
            return _getSimpleProperty(bean, name);
        } catch (final RuntimeException e) {
            throw e;
        } catch (final Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Object _getSimpleProperty(final Object bean, final String name) throws Exception {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        }

        // Validate the syntax of the property name
        if (resolver.hasNested(name)) {
            throw new IllegalArgumentException("Nested property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
        } else if (resolver.isIndexed(name)) {
            throw new IllegalArgumentException("Indexed property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
        } else if (resolver.isMapped(name)) {
            throw new IllegalArgumentException("Mapped property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
        }

        if (name.startsWith("@")) {
            final String fieldName = trimAnnotations(name);
            final Field f = FieldUtils.getField(bean.getClass(), fieldName);
            if (null == f) {
                throw new NoSuchFieldException("field `" + fieldName + "` not found");
            }
            f.setAccessible(true);
            return f.get(bean);
        } else {
            // Retrieve the property getter method for the specified property
            final PropertyDescriptor descriptor = getPropertyDescriptor(bean, trimAnnotations(name));
            if (descriptor == null) {
                throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + bean.getClass() + "'");
            }
            final Method readMethod = getReadMethod(bean.getClass(), descriptor);
            if (readMethod == null) {
                throw new NoSuchMethodException("Property '" + name + "' has no getter method in class '" + bean.getClass() + "'");
            }

            // Call the property getter and return the value
            final Object value = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
            return value;
        }

    }

    /**
     * <p>
     * Return an accessible property setter method for this property, if there
     * is one; otherwise return <code>null</code>.
     * </p>
     *
     * <p>
     * <em>Note:</em> This method does not work correctly with custom bean
     * introspection under certain circumstances. It may return {@code null}
     * even if a write method is defined for the property in question. Use
     * {@link #getWriteMethod(Class, PropertyDescriptor)} to be sure that the
     * correct result is returned.
     * </p>
     * <p>
     * <strong>FIXME</strong> - Does not work with DynaBeans.
     * </p>
     *
     * @param descriptor Property descriptor to return a setter for
     * @return The write method
     */
    public Method getWriteMethod(final PropertyDescriptor descriptor) {

        return MethodUtils.getAccessibleMethod(descriptor.getWriteMethod());

    }

    /**
     * <p>
     * Return an accessible property setter method for this property, if there
     * is one; otherwise return <code>null</code>.
     * </p>
     *
     * <p>
     * <strong>FIXME</strong> - Does not work with DynaBeans.
     * </p>
     *
     * @param clazz      The class of the read method will be invoked on
     * @param descriptor Property descriptor to return a setter for
     * @return The write method
     * @since 1.9.1
     */
    public Method getWriteMethod(final Class<?> clazz, final PropertyDescriptor descriptor) {

        final BeanIntrospectionData data = getIntrospectionData(clazz);
        return MethodUtils.getAccessibleMethod(data.getWriteMethod(clazz, descriptor));
    }

    /**
     * <p>
     * Return <code>true</code> if the specified property name identifies a
     * readable property on the specified bean; otherwise, return
     * <code>false</code>.
     *
     * @param bean Bean to be examined
     * @param name Property name to be evaluated
     * @return <code>true</code> if the property is readable, otherwise
     * <code>false</code>
     * @throws IllegalArgumentException if <code>bean</code> or <code>name</code> is
     *                                  <code>null</code>
     * @since BeanUtils 1.6
     */
    public boolean isReadable(Object bean, String name) {

        // Validate method parameters
        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        }

        // Resolve nested references
        while (resolver.hasNested(name)) {
            final String next = resolver.next(name);
            Object nestedBean = null;
            try {
                nestedBean = getProperty(bean, next);
            } catch (final Exception e) {
                return false;
            }
            if (nestedBean == null) {
                throw new NestedNullException("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'");
            }
            bean = nestedBean;
            name = resolver.remove(name);
        }

        // Remove any subscript from the final name value
        name = resolver.getProperty(name);

        // Return the requested result
        {
            try {
                final PropertyDescriptor desc = getPropertyDescriptor(bean, name);
                if (desc != null) {
                    Method readMethod = getReadMethod(bean.getClass(), desc);
                    if (readMethod == null) {
                        if (desc instanceof IndexedPropertyDescriptor) {
                            readMethod = ((IndexedPropertyDescriptor) desc).getIndexedReadMethod();
                        } else if (desc instanceof MappedPropertyDescriptor) {
                            readMethod = ((MappedPropertyDescriptor) desc).getMappedReadMethod();
                        }
                        readMethod = MethodUtils.getAccessibleMethod(readMethod);
                    }
                    return readMethod != null;
                } else {
                    return false;
                }
            } catch (final IllegalAccessException e) {
                return false;
            } catch (final InvocationTargetException e) {
                return false;
            } catch (final NoSuchMethodException e) {
                return false;
            }
        }

    }

    /**
     * <p>
     * Return <code>true</code> if the specified property name identifies a
     * writeable property on the specified bean; otherwise, return
     * <code>false</code>.
     *
     * @param bean Bean to be examined (may be a
     * @param name Property name to be evaluated
     * @return <code>true</code> if the property is writeable, otherwise
     * <code>false</code>
     * @throws IllegalArgumentException if <code>bean</code> or <code>name</code> is
     *                                  <code>null</code>
     * @since BeanUtils 1.6
     */
    public boolean isWriteable(Object bean, String name) {

        // Validate method parameters
        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        }

        // Resolve nested references
        while (resolver.hasNested(name)) {
            final String next = resolver.next(name);
            Object nestedBean = null;
            try {
                nestedBean = getProperty(bean, next);
            } catch (final Exception e) {
                return false;
            }
            if (nestedBean == null) {
                throw new NestedNullException("Null property value for '" + next + "' on bean class '" + bean.getClass() + "'");
            }
            bean = nestedBean;
            name = resolver.remove(name);
        }

        // Remove any subscript from the final name value
        name = resolver.getProperty(name);

        {
            try {
                final PropertyDescriptor desc = getPropertyDescriptor(bean, name);
                if (desc != null) {
                    Method writeMethod = getWriteMethod(bean.getClass(), desc);
                    if (writeMethod == null) {
                        if (desc instanceof IndexedPropertyDescriptor) {
                            writeMethod = ((IndexedPropertyDescriptor) desc).getIndexedWriteMethod();
                        } else if (desc instanceof MappedPropertyDescriptor) {
                            writeMethod = ((MappedPropertyDescriptor) desc).getMappedWriteMethod();
                        }
                        writeMethod = MethodUtils.getAccessibleMethod(writeMethod);
                    }
                    return writeMethod != null;
                } else {
                    return false;
                }
            } catch (final IllegalAccessException e) {
                return false;
            } catch (final InvocationTargetException e) {
                return false;
            } catch (final NoSuchMethodException e) {
                return false;
            }
        }

    }

    /**
     * Set the value of the specified indexed property of the specified bean,
     * with no type conversions. The zero-relative index of the required value
     * must be included (in square brackets) as a suffix to the property name,
     * or <code>IllegalArgumentException</code> will be thrown. In addition to
     * supporting the JavaBeans specification, this method has been extended to
     * support <code>List</code> objects as well.
     *
     * @param bean  Bean whose property is to be modified
     * @param name  <code>propertyname[index]</code> of the property value to be
     *              modified
     * @param value Value to which the specified property element should be set
     * @throws IndexOutOfBoundsException if the specified index is outside the valid range for the
     *                                   underlying property
     * @throws IllegalAccessException    if the caller does not have access to the property accessor
     *                                   method
     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
     * @throws InvocationTargetException if the property accessor method throws an exception
     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
     */
    public void setIndexedProperty(final Object bean, String name, final Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        try {
            _setIndexedProperty(bean, name, value);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void _setIndexedProperty(final Object bean, String name, final Object value) throws Exception {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        }

        // Identify the index of the requested individual property
        int index = -1;
        try {
            index = resolver.getIndex(name);
        } catch (final IllegalArgumentException e) {
            throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "'");
        }
        if (index < 0) {
            throw new IllegalArgumentException("Invalid indexed property '" + name + "' on bean class '" + bean.getClass() + "'");
        }

        // Isolate the name
        name = resolver.getProperty(name);

        // Set the specified indexed property value
        _setIndexedProperty(bean, name, index, value);

    }

    /**
     * Set the value of the specified indexed property of the specified bean,
     * with no type conversions. In addition to supporting the JavaBeans
     * specification, this method has been extended to support <code>List</code>
     * objects as well.
     *
     * @param bean  Bean whose property is to be set
     * @param name  Simple property name of the property value to be set
     * @param index Index of the property value to be set
     * @param value Value to which the indexed property element is to be set
     * @throws IndexOutOfBoundsException if the specified index is outside the valid range for the
     *                                   underlying property
     * @throws IllegalAccessException    if the caller does not have access to the property accessor
     *                                   method
     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
     * @throws InvocationTargetException if the property accessor method throws an exception
     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
     */
    public void setIndexedProperty(final Object bean, final String name, final int index, final Object value) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        try {
            _setIndexedProperty(bean, name, index, value);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void _setIndexedProperty(final Object bean, final String name, final int index, final Object value) throws Exception {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null || name.length() == 0) {
            if (bean.getClass().isArray()) {
                Array.set(bean, index, value);
                return;
            } else if (bean instanceof List) {
                final List<Object> list = toObjectList(bean);
                list.set(index, value);
                return;
            }
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        }

        if (name.startsWith("@")) {
            Object f = FieldUtils.readField(bean, name.substring(1));
            if (null == f) {
                throw new NestedNullException();
            }
            if (f.getClass().isArray()) {
                Array.set(f, index, value);
                return;
            } else if (f instanceof List) {
                final List<Object> list = toObjectList(f);
                list.set(index, value);
                return;
            }
            return;
        }
        // Retrieve the property descriptor for the specified property
        final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
        if (descriptor == null) {
            throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
        }

        // Call the indexed setter method if there is one
        if (descriptor instanceof IndexedPropertyDescriptor) {
            Method writeMethod = ((IndexedPropertyDescriptor) descriptor).getIndexedWriteMethod();
            writeMethod = MethodUtils.getAccessibleMethod(writeMethod);
            if (writeMethod != null) {
                final Object[] subscript = new Object[2];
                subscript[0] = index;
                subscript[1] = value;
                try {
                    if (log.isTraceEnabled()) {
                        final String valueClassName = value == null ? "<null>" : value.getClass().getName();
                        log.trace("setSimpleProperty: Invoking method " + writeMethod + " with index=" + index + ", value=" + value + " (class " + valueClassName + ")");
                    }
                    invokeMethod(writeMethod, bean, subscript);
                } catch (final InvocationTargetException e) {
                    if (e.getTargetException() instanceof IndexOutOfBoundsException) {
                        throw (IndexOutOfBoundsException) e.getTargetException();
                    } else {
                        throw e;
                    }
                }
                return;
            }
        }

        // Otherwise, the underlying property must be an array or a list
        final Method readMethod = getReadMethod(bean.getClass(), descriptor);
        if (readMethod == null) {
            throw new NoSuchMethodException("Property '" + name + "' has no getter method on bean class '" + bean.getClass() + "'");
        }

        // Call the property getter to get the array or list
        final Object array = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
        if (!array.getClass().isArray()) {
            if (array instanceof List) {
                // Modify the specified value in the List
                final List<Object> list = toObjectList(array);
                list.set(index, value);
            } else {
                throw new IllegalArgumentException("Property '" + name + "' is not indexed on bean class '" + bean.getClass() + "'");
            }
        } else {
            // Modify the specified value in the array
            Array.set(array, index, value);
        }

    }

    /**
     * Set the value of the specified mapped property of the specified bean,
     * with no type conversions. The key of the value to set must be included
     * (in brackets) as a suffix to the property name, or
     * <code>IllegalArgumentException</code> will be thrown.
     *
     * @param bean  Bean whose property is to be set
     * @param name  <code>propertyname(key)</code> of the property value to be set
     * @param value The property value to be set
     * @throws IllegalAccessException    if the caller does not have access to the property accessor
     *                                   method
     * @throws InvocationTargetException if the property accessor method throws an exception
     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
     */
    public void setMappedProperty(final Object bean, String name, final Object value) {
        try {
            _setMappedProperty(bean, name, value);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void _setMappedProperty(final Object bean, String name, final Object value) throws Exception {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        }

        // Identify the key of the requested individual property
        String key = null;
        try {
            key = resolver.getKey(name);
        } catch (final IllegalArgumentException e) {
            throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "'");
        }
        if (key == null) {
            throw new IllegalArgumentException("Invalid mapped property '" + name + "' on bean class '" + bean.getClass() + "'");
        }

        // Isolate the name
        name = resolver.getProperty(name);

        // Request the specified indexed property value
        _setMappedProperty(bean, name, key, value);

    }

    /**
     * Set the value of the specified mapped property of the specified bean,
     * with no type conversions.
     *
     * @param bean  Bean whose property is to be set
     * @param name  Mapped property name of the property value to be set
     * @param key   Key of the property value to be set
     * @param value The property value to be set
     * @throws IllegalAccessException    if the caller does not have access to the property accessor
     *                                   method
     * @throws InvocationTargetException if the property accessor method throws an exception
     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
     * @throws NoSuchFieldException
     */
    public void setMappedProperty(final Object bean, final String name, final String key, final Object value) {
        try {
            _setMappedProperty(bean, name, key, value);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void _setMappedProperty(final Object bean, final String name, final String key, final Object value) throws Exception {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        }
        if (key == null) {
            throw new IllegalArgumentException("No key specified for property '" + name + "' on bean class '" + bean.getClass() + "'");
        }

        if (name.startsWith("@")) {
            Object f = FieldUtils.readField(bean, name.substring(1));
            if (null == f) {
                throw new NestedNullException();
            }
            if (f instanceof Map) {
                final Map<String, Object> map = toPropertyMap(f);
                map.put(key, value);
                return;
            }
            throw new NoSuchFieldException("Field '" + name + "' is not mapped " + "on bean class '" + bean.getClass() + "'");
        }
        // Retrieve the property descriptor for the specified property
        final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
        if (descriptor == null) {
            throw new NoSuchMethodException("Unknown property '" + name + "' on bean class '" + bean.getClass() + "'");
        }

        if (descriptor instanceof MappedPropertyDescriptor) {
            // Call the keyed setter method if there is one
            Method mappedWriteMethod = ((MappedPropertyDescriptor) descriptor).getMappedWriteMethod();
            mappedWriteMethod = MethodUtils.getAccessibleMethod(mappedWriteMethod);
            if (mappedWriteMethod != null) {
                final Object[] params = new Object[2];
                params[0] = key;
                params[1] = value;
                if (log.isTraceEnabled()) {
                    final String valueClassName = value == null ? "<null>" : value.getClass().getName();
                    log.trace("setSimpleProperty: Invoking method " + mappedWriteMethod + " with key=" + key + ", value=" + value + " (class " + valueClassName + ")");
                }
                invokeMethod(mappedWriteMethod, bean, params);
            } else {
                throw new NoSuchMethodException("Property '" + name + "' has no mapped setter method" + "on bean class '" + bean.getClass() + "'");
            }
        } else {
            /* means that the result has to be retrieved from a map */
            final Method readMethod = getReadMethod(bean.getClass(), descriptor);
            if (readMethod != null) {
                final Object invokeResult = invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY);
                /* test and fetch from the map */
                if (invokeResult instanceof Map) {
                    final Map<String, Object> map = toPropertyMap(invokeResult);
                    map.put(key, value);
                }
            } else {
                throw new NoSuchMethodException("Property '" + name + "' has no mapped getter method on bean class '" + bean.getClass() + "'");
            }
        }

    }

    /**
     * Set the value of the (possibly nested) property of the specified name,
     * for the specified bean, with no type conversions.
     * <p>
     * Example values for parameter "name" are:
     * <ul>
     * <li>"a" -- sets the value of property a of the specified bean</li>
     * <li>"a.b" -- gets the value of property a of the specified bean, then on
     * that object sets the value of property b.</li>
     * <li>"a(key)" -- sets a value of mapped-property a on the specified bean.
     * This effectively means bean.setA("key").</li>
     * <li>"a[3]" -- sets a value of indexed-property a on the specified bean.
     * This effectively means bean.setA(3).</li>
     * </ul>
     *
     * @param bean  Bean whose property is to be modified
     * @param name  Possibly nested name of the property to be modified
     * @param value Value to which the property is to be set
     * @throws IllegalAccessException    if the caller does not have access to the property accessor
     *                                   method
     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
     * @throws IllegalArgumentException  if a nested reference to a property returns null
     * @throws InvocationTargetException if the property accessor method throws an exception
     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
     */
    public void setNestedProperty(Object bean, String name, final Object value) {
        try {
            _setNestedProperty(bean, name, value);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void _setNestedProperty(Object bean, String name, final Object value) throws Exception {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        }

        // Resolve nested references
        while (resolver.hasNested(name)) {
            final String next = resolver.next(name);
            Object nestedBean = null;
            if (bean instanceof Map) {
                nestedBean = getPropertyOfMapBean((Map<?, ?>) bean, next);
            } else if (resolver.isMapped(next)) {
                nestedBean = getMappedProperty(bean, next);
            } else if (resolver.isIndexed(next)) {
                nestedBean = _getIndexedProperty(bean, next);
            } else {
                nestedBean = _getSimpleProperty(bean, next);
            }
            if (nestedBean == null) {
                throw new NestedNullException("Null property value for '" + name + "' on bean class '" + bean.getClass() + "'");
            }
            bean = nestedBean;
            name = resolver.remove(name);
        }

        if (bean instanceof Map) {
            setPropertyOfMapBean(toPropertyMap(bean), name, value);
        } else if (resolver.isMapped(name)) {
            _setMappedProperty(bean, name, value);
        } else if (resolver.isIndexed(name)) {
            _setIndexedProperty(bean, name, value);
        } else {
            _setSimpleProperty(bean, name, value);
        }

    }

    /**
     * This method is called by method setNestedProperty when the current bean
     * is found to be a Map object, and defines how to deal with setting a
     * property on a Map.
     * <p>
     * The standard implementation here is to:
     * <ul>
     * <li>call bean.set(propertyName) for all propertyName values.</li>
     * <li>throw an IllegalArgumentException if the property specifier contains
     * MAPPED_DELIM or INDEXED_DELIM, as Map entries are essentially simple
     * properties; mapping and indexing operations do not make sense when
     * accessing a map (even thought the returned object may be a Map or an
     * Array).</li>
     * </ul>
     * <p>
     * The default behaviour of beanutils 1.7.1 or later is for assigning to
     * "a.b" to mean a.put(b, obj) always. However the behaviour of beanutils
     * version 1.6.0, 1.6.1, 1.7.0 was for "a.b" to mean a.setB(obj) if such a
     * method existed, and a.put(b, obj) otherwise. In version 1.5 it meant
     * a.put(b, obj) always (ie the same as the behaviour in the current
     * version). In versions prior to 1.5 it meant a.setB(obj) always. [yes,
     * this is all <i>very</i> unfortunate]
     * <p>
     * Users who would like to customise the meaning of "a.b" in method
     * setNestedProperty when a is a Map can create a custom subclass of this
     * class and override this method to implement the behaviour of their
     * choice, such as restoring the pre-1.4 behaviour of this class if they
     * wish. When overriding this method, do not forget to deal with
     * MAPPED_DELIM and INDEXED_DELIM characters in the propertyName.
     * <p>
     * Note, however, that the recommended solution for objects that implement
     * Map but want their simple properties to come first is for <i>those</i>
     * objects to override their get/put methods to implement that behaviour,
     * and <i>not</i> to solve the problem by modifying the default behaviour of
     * the PropertyUtilsBean class by overriding this method.
     *
     * @param bean         Map bean
     * @param propertyName The property name
     * @param value        the property value
     * @throws IllegalArgumentException  when the propertyName is regarded as being invalid.
     * @throws IllegalAccessException    just in case subclasses override this method to try to access
     *                                   real setter methods and find permission is denied.
     * @throws InvocationTargetException just in case subclasses override this method to try to access
     *                                   real setter methods, and find it throws an exception when
     *                                   invoked.
     * @throws NoSuchMethodException     just in case subclasses override this method to try to access
     *                                   real setter methods, and want to fail if no simple method is
     *                                   available.
     * @since 1.8.0
     */
    protected void setPropertyOfMapBean(final Map<String, Object> bean, String propertyName, final Object value) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {

        if (resolver.isMapped(propertyName)) {
            final String name = resolver.getProperty(propertyName);
            if (name == null || name.length() == 0) {
                propertyName = resolver.getKey(propertyName);
            }
        }

        if (resolver.isIndexed(propertyName) || resolver.isMapped(propertyName)) {
            throw new IllegalArgumentException("Indexed or mapped properties are not supported on" + " objects of type Map: " + propertyName);
        }

        bean.put(propertyName, value);
    }

    /**
     * Set the value of the specified property of the specified bean, no matter
     * which property reference format is used, with no type conversions.
     *
     * @param bean  Bean whose property is to be modified
     * @param name  Possibly indexed and/or nested name of the property to be
     *              modified
     * @param value Value to which this property is to be set
     */
    public void setProperty(final Object bean, final String name, final Object value) {

        setNestedProperty(bean, name, value);

    }

    /**
     * Set the value of the specified simple property of the specified bean,
     * with no type conversions.
     *
     * @param bean  Bean whose property is to be modified
     * @param name  Name of the property to be modified
     * @param value Value to which the property should be set
     * @throws IllegalAccessException    if the caller does not have access to the property accessor
     *                                   method
     * @throws IllegalArgumentException  if <code>bean</code> or <code>name</code> is null
     * @throws IllegalArgumentException  if the property name is nested or indexed
     * @throws InvocationTargetException if the property accessor method throws an exception
     * @throws NoSuchMethodException     if an accessor method for this propety cannot be found
     */
    public void setSimpleProperty(final Object bean, final String name, final Object value) {
        try {
            _setSimpleProperty(bean, name, value);
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void _setSimpleProperty(final Object bean, final String name, final Object value) throws Exception {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified");
        }
        if (name == null) {
            throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'");
        }

        // Validate the syntax of the property name
        if (resolver.hasNested(name)) {
            throw new IllegalArgumentException("Nested property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
        } else if (resolver.isIndexed(name)) {
            throw new IllegalArgumentException("Indexed property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
        } else if (resolver.isMapped(name)) {
            throw new IllegalArgumentException("Mapped property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'");
        }

        if (name.startsWith("@")) {
            FieldUtils.writeField(bean, name.substring(1), value);
            return;
        }
        // Retrieve the property setter method for the specified property
        final PropertyDescriptor descriptor = getPropertyDescriptor(bean, name);
        if (descriptor == null) {
            throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + bean.getClass() + "'");
        }
        final Method writeMethod = getWriteMethod(bean.getClass(), descriptor);
        if (writeMethod == null) {
            throw new NoSuchMethodException("Property '" + name + "' has no setter method in class '" + bean.getClass() + "'");
        }

        // Call the property setter method
        final Object[] values = new Object[1];
        values[0] = value;
        if (log.isTraceEnabled()) {
            final String valueClassName = value == null ? "<null>" : value.getClass().getName();
            log.trace("setSimpleProperty: Invoking method " + writeMethod + " with value " + value + " (class " + valueClassName + ")");
        }
        invokeMethod(writeMethod, bean, values);

    }

    /**
     * This just catches and wraps IllegalArgumentException.
     */
    private Object invokeMethod(final Method method, final Object bean, final Object[] values) throws IllegalAccessException, InvocationTargetException {

        if (bean == null) {
            throw new IllegalArgumentException("No bean specified " + "- this should have been checked before reaching this method");
        }

        try {

            return method.invoke(bean, values);

        } catch (final NullPointerException cause) {
            // JDK 1.3 and JDK 1.4 throw NullPointerException if an argument is
            // null for a primitive value (JDK 1.5+ throw
            // IllegalArgumentException)
            StringBuilder valueString = new StringBuilder();
            if (values != null) {
                for (int i = 0; i < values.length; i++) {
                    if (i > 0) {
                        valueString.append(", ");
                    }
                    if (values[i] == null) {
                        valueString.append("<null>");
                    } else {
                        valueString.append(values[i].getClass().getName());
                    }
                }
            }
            StringBuilder expectedString = new StringBuilder();
            final Class<?>[] parTypes = method.getParameterTypes();
            for (int i = 0; i < parTypes.length; i++) {
                if (i > 0) {
                    expectedString.append(", ");
                }
                expectedString.append(parTypes[i].getName());
            }
            final IllegalArgumentException e = new IllegalArgumentException("Cannot invoke " + method.getDeclaringClass().getName() + "." + method.getName() + " on bean class '" + bean.getClass() + "' - " + cause.getMessage()
                    // as per
                    // https://issues.apache.org/jira/browse/BEANUTILS-224
                    + " - had objects of type \"" + valueString + "\" but expected signature \"" + expectedString + "\"");
            if (!initCause(e, cause)) {
                log.error("Method invocation failed", cause);
            }
            throw e;
        } catch (final IllegalArgumentException cause) {
            StringBuilder valueString = new StringBuilder();
            if (values != null) {
                for (int i = 0; i < values.length; i++) {
                    if (i > 0) {
                        valueString.append(", ");
                    }
                    if (values[i] == null) {
                        valueString.append("<null>");
                    } else {
                        valueString.append(values[i].getClass().getName());
                    }
                }
            }
            StringBuilder expectedString = new StringBuilder();
            final Class<?>[] parTypes = method.getParameterTypes();
            for (int i = 0; i < parTypes.length; i++) {
                if (i > 0) {
                    expectedString.append(", ");
                }
                expectedString.append(parTypes[i].getName());
            }
            final IllegalArgumentException e = new IllegalArgumentException("Cannot invoke " + method.getDeclaringClass().getName() + "." + method.getName() + " on bean class '" + bean.getClass() + "' - " + cause.getMessage()
                    // as per
                    // https://issues.apache.org/jira/browse/BEANUTILS-224
                    + " - had objects of type \"" + valueString + "\" but expected signature \"" + expectedString + "\"");
            if (!initCause(e, cause)) {
                log.error("Method invocation failed", cause);
            }
            throw e;

        }
    }

    /**
     * Obtains the {@code BeanIntrospectionData} object describing the specified
     * bean class. This object is looked up in the internal cache. If necessary,
     * introspection is performed now on the affected bean class, and the
     * results object is created.
     *
     * @param beanClass the bean class in question
     * @return the {@code BeanIntrospectionData} object for this class
     * @throws IllegalArgumentException if the bean class is <b>null</b>
     */
    private BeanIntrospectionData getIntrospectionData(final Class<?> beanClass) {

        if (beanClass == null) {
            throw new IllegalArgumentException("No bean class specified");
        }

        // Look up any cached information for this bean class
        BeanIntrospectionData data = descriptorsCache.get(beanClass);
        if (data == null) {
            data = fetchIntrospectionData(beanClass);
            descriptorsCache.put(beanClass, data);
        }

        return data;
    }

    /**
     * Performs introspection on the specified class. This method invokes all
     * {@code BeanIntrospector} objects that were added to this instance.
     *
     * @param beanClass the class to be inspected
     * @return a data object with the results of introspection
     */
    private BeanIntrospectionData fetchIntrospectionData(final Class<?> beanClass) {

        final DefaultIntrospectionContext ictx = new DefaultIntrospectionContext(beanClass);

        for (final BeanIntrospector bi : introspectors) {
            try {
                bi.introspect(ictx);
            } catch (final IntrospectionException iex) {
                log.error("Exception during introspection", iex);
            }
        }

        return new BeanIntrospectionData(ictx.getPropertyDescriptors());
    }

    /**
     * Converts an object to a list of objects. This method is used when dealing
     * with indexed properties. It assumes that indexed properties are stored as
     * lists of objects.
     *
     * @param obj the object to be converted
     * @return the resulting list of objects
     */
    private static List<Object> toObjectList(final Object obj) {

        @SuppressWarnings("unchecked") final
        // indexed properties are stored in lists of objects
        List<Object> list = (List<Object>) obj;
        return list;
    }

    /**
     * Converts an object to a map with property values. This method is used
     * when dealing with mapped properties. It assumes that mapped properties
     * are stored in a Map&lt;String, Object&gt;.
     *
     * @param obj the object to be converted
     * @return the resulting properties map
     */
    private static Map<String, Object> toPropertyMap(final Object obj) {

        @SuppressWarnings("unchecked") final
        // mapped properties are stores in maps of type <String, Object>
        Map<String, Object> map = (Map<String, Object>) obj;
        return map;
    }

    /**
     * If we're running on JDK 1.4 or later, initialize the cause for the given
     * throwable.
     *
     * @param throwable The throwable.
     * @param cause     The cause of the throwable.
     * @return true if the cause was initialized, otherwise false.
     * @since 1.8.0
     */
    public boolean initCause(final Throwable throwable, final Throwable cause) {
        if (INIT_CAUSE_METHOD != null && cause != null) {
            try {
                INIT_CAUSE_METHOD.invoke(throwable, cause);
                return true;
            } catch (final Throwable e) {
                return false; // can't initialize cause
            }
        }
        return false;
    }

    /**
     * A reference to Throwable's initCause method, or null if it's not there in
     * this JVM
     */
    private static final Method INIT_CAUSE_METHOD = getInitCauseMethod();

    /**
     * Returns a <code>Method<code> allowing access to
     * {@link Throwable#initCause(Throwable)} method of {@link Throwable},
     * or <code>null</code> if the method does not exist.
     *
     * @return A <code>Method<code> for <code>Throwable.initCause</code>, or
     * <code>null</code> if unavailable.
     */
    private static Method getInitCauseMethod() {
        try {
            final Class<?>[] paramsClasses = new Class<?>[]{Throwable.class};
            return Throwable.class.getMethod("initCause", paramsClasses);
        } catch (final NoSuchMethodException e) {
            final Logger log = LoggerFactory.getLogger(PropertyUtils.class);
            if (log.isWarnEnabled()) {
                log.warn("Throwable does not have initCause() method in JDK 1.3");
            }
            return null;
        } catch (final Throwable e) {
            final Logger log = LoggerFactory.getLogger(PropertyUtils.class);
            if (log.isWarnEnabled()) {
                log.warn("Error getting the Throwable initCause() method", e);
            }
            return null;
        }
    }
}
